﻿using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace Tsanie.ShellExt.Definitions
{

    #region - Structs/Enums -

    public enum NotifyResult
    {
        NoError = 0,
        Invalid = 1,
        InvalidNoChangePage = 2
    }

    public enum PSP_FLAGS
    {
        PSP_DEFAULT = 0x0,
        PSP_DLGINDIRECT = 0x1,
        PSP_USEHICON = 0x2,
        PSP_USEICONID = 0x4,
        PSP_USETITLE = 0x8,
        PSP_RTLREADING = 0x10,
        PSP_HASHELP = 0x20,
        PSP_USEREFPARENT = 0x40,
        PSP_USECALLBACK = 0x80,
        PSP_PREMATURE = 0x400,
        PSP_HIDEHEADER = 0x800,
        PSP_USEHEADERTITLE = 0x1000,
        PSP_USEHEADERSUBTITLE = 0x2000
    }

    public enum PSPCALLBACKMESSAGES
    {
        PSPCB_RELEASE = 1,
        PSPCB_CREATE = 2
    }

    public delegate int WndProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);

    [return: MarshalAs(UnmanagedType.Bool)]
    public delegate Boolean PropertySheetCallback(IntPtr hWnd, PSPCALLBACKMESSAGES Msg, ref PROPSHEETPAGE Page);

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct PROPSHEETPAGE
    {
        public int dwSize;
        public PSP_FLAGS dwFlags;
        public int hInstance;
        public IntPtr pResource;
        public IntPtr hIcon;
        public String pszTitle;
        [MarshalAs(UnmanagedType.FunctionPtr)]
        public WndProc pfnDlgProc;
        public int lParam;
        [MarshalAs(UnmanagedType.FunctionPtr)]
        public PropertySheetCallback pfnCallback;
        public int pcRefParent;
    }

    public struct DLGTEMPLATE
    {
        public int Style;
        public int dwExtendedStyle;
        public short cdit;
        public short x;
        public short y;
        public short cx;
        public short cy;
        public short Menu;
        [MarshalAs(UnmanagedType.LPStr, SizeConst = 7)]
        public String Class;
        public int Caption;
    }

    public enum PSNOTIFYCODES
    {
        PSN_FIRST = (-200),
        PSN_LAST = (-299),
        PSN_SETACTIVE = (-200),
        PSN_KILLACTIVE = (-201),
        PSN_APPLY = (-202),
        PSN_RESET = (-203),
        PSN_HELP = (-205),
        PSN_WIZBACK = (-206),
        PSN_WIZNEXT = (-207),
        PSN_WIZFINISH = (-208),
        PSN_QUERYCANCEL = (-209),
        PSN_GETOBJECT = (-210)
    }

    public struct PSHNOTIFY
    {
        public IntPtr hwndFrom;
        public int idFrom;
        public PSNOTIFYCODES code;
        public IntPtr lParam;
    }

    #endregion

    public abstract class PropertySheetBase : IShellExtInit, IShellPropSheetExt
    {

        #region - 私有字段 -

        FileCollection m_Files;
        PROPSHEETPAGE m_PropSheet;
        IntPtr m_SheetHandle;
        IntPtr m_DlgHandle;

        #endregion

        #region - 构造 -

        protected PropertySheetBase() { }

        #endregion

        protected abstract PropertySheetTemplate OnGetTemplate();
        protected abstract void OnInitFiles();

        #region - APIs -

        enum WindowStyles
        {
            WS_VISIBLE = 0x10000000,
            WS_TABSTOP = 0x10000
        }
        enum WindowMessages
        {
            WM_SIZE = 5,
            WM_PAINT = 0xF,
            WM_NOTIFY = 0x4E,
            WM_INITDIALOG = 0x110,
            PSM_CHANGED = 0x400 + 104,
            PSM_UNCHANGED = 0x400 + 109,
            PSM_APPLY = 0x400 + 110,
            PSM_CANCELTOCLOSE = 0x400 + 107
        }

        [DllImport("user32.dll")]
        extern static IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
        [DllImport("user32.dll")]
        extern static IntPtr GetParent(IntPtr hwnd);
        [DllImport("user32.dll", CharSet = CharSet.Ansi, EntryPoint = "SendMessageA")]
        extern static int SendMessage(IntPtr hwnd, int Msg, IntPtr wParam, IntPtr lParam);
        [DllImport("comctl32.dll", CharSet = CharSet.Unicode)]
        extern static IntPtr CreatePropertySheetPageW(ref PROPSHEETPAGE PSP);
        [DllImport("comctl32.dll")]
        extern static int DestroyPropertySheetPage(IntPtr hPage);
        [DllImport("user32.dll")]
        extern static int GetClientRect(IntPtr hWnd, ref Rectangle r);

        #endregion

        #region - 公共属性 -

        public FileCollection Files { get { return m_Files; } }
        public IntPtr DialogHandle { get { return m_DlgHandle; } }
        public IntPtr SheetHandle { get { return m_SheetHandle; } }

        #endregion

        #region - 公共方法 -

        public void Changed()
        {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_CHANGED, m_SheetHandle, IntPtr.Zero);
        }
        public void Reset()
        {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_UNCHANGED, m_SheetHandle, IntPtr.Zero);
        }
        public void Apply()
        {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_APPLY, m_SheetHandle, IntPtr.Zero);
        }
        public void CancelToClose()
        {
            PropertySheetBase.SendMessage(m_DlgHandle, (int)WindowMessages.PSM_CANCELTOCLOSE, IntPtr.Zero, IntPtr.Zero);
        }

        #endregion

        #region - 子类重写 -

        protected virtual bool AllowMultiFiles { get { return false; } }
        //protected virtual string[] AllowExtensions { get { return new string[0]; } }

        protected virtual void OnCreatePage() { }
        protected virtual NotifyResult OnCancel() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnActivate() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnApply() { return NotifyResult.NoError; }
        protected virtual NotifyResult OnShowHelp() { return NotifyResult.NoError; }

        #endregion

        #region - 私有方法 -

        bool Callback(IntPtr hWnd, PSPCALLBACKMESSAGES Msg, ref PROPSHEETPAGE Page)
        {
            switch (Msg)
            {
                case PSPCALLBACKMESSAGES.PSPCB_CREATE:
                    //#if DEBUGPROPERTY
                    //                    Common.OutputDebugString("Callback(IntPtr, PSPCALLBACKMESSAGES, ref PROPSHEETPAGE):: PSPCB_CREATE.");
                    //#endif
                    break;

                case PSPCALLBACKMESSAGES.PSPCB_RELEASE:
#if DEBUGPROPERTY
                    Common.OutputDebugString("Callback(IntPtr, PSPCALLBACKMESSAGES, ref PROPSHEETPAGE):: PSPCB_RELEASE");
#endif
                    Marshal.FreeCoTaskMem(m_PropSheet.pResource);
                    PropertySheetTemplate template = OnGetTemplate();
                    if (template.Icon != null)
                    {
                        template.Icon.Dispose();
                        template.Icon = null;
                    }
                    m_Files.Dispose();
                    m_Files = null;
                    template.Dispose();
                    GC.SuppressFinalize(this);
                    GC.Collect();
                    break;
            }
            return true;
        }
        int PageProc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)
        {
            switch (Msg)
            {
                case (int)WindowMessages.WM_INITDIALOG:
#if DEBUGPROPERTY
                    Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: WM_INITDIALOG.");
#endif
                    PropertySheetTemplate template = OnGetTemplate();
                    SetParent(template.Handle, hWnd);
                    m_SheetHandle = hWnd;
                    m_DlgHandle = GetParent(hWnd);
                    template.Visible = true;
                    return 1;

                case (int)WindowMessages.WM_NOTIFY:
                    PSHNOTIFY notify = (PSHNOTIFY)Marshal.PtrToStructure(lParam, typeof(PSHNOTIFY));
                    switch (notify.code)
                    {
                        case PSNOTIFYCODES.PSN_SETACTIVE:
#if DEBUGPROPERTY
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_SETACTIVE.");
#endif
                            return (int)this.OnActivate();

                        case PSNOTIFYCODES.PSN_APPLY:
#if DEBUGPROPERTY
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_APPLY.");
#endif
                            return (int)this.OnApply();

                        case PSNOTIFYCODES.PSN_RESET:
#if DEBUGPROPERTY
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_RESET.");
#endif
                            return (int)this.OnCancel();

                        case PSNOTIFYCODES.PSN_HELP:
#if DEBUGPROPERTY
                            Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: PSN_HELP.");
#endif
                            return (int)this.OnShowHelp();

                    }
                    return 1;

                case (int)WindowMessages.WM_SIZE:
                    int i = lParam.ToInt32();
                    int width = i & 0xFFFF;
                    int height = i / 0x10000;
                    OnGetTemplate().Size = new Size(width, height);
#if DEBUGPROPERTY
                    Common.OutputDebugString("PageProc(IntPtr, int, IntPtr, IntPtr):: WM_SIZE, Width = {0}, Height = {1}", width, height);
#endif
                    this.OnCreatePage();
                    return 0;
            }
            return 0;
        }

        #endregion

        #region - IShellPropSheetExt 成员 -

        int IShellPropSheetExt.AddPages([MarshalAs(UnmanagedType.FunctionPtr)] AddPageDelegate lpfnAddPage, IntPtr lParam)
        {
            if (m_Files.Count < 1)
            {
                Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... FileCollection.Count = 0");
                return 1;
            }
            else if (m_Files.Count > 1)
            {
                if (this.AllowMultiFiles)
                {
                    //string[] allowExtensions = this.AllowExtensions;
                    //if (allowExtensions.Length > 0)
                    //{
                    //    foreach (string file in m_Files)
                    //    {
                    //        bool allow = false;
                    //        foreach (string ext in allowExtensions)
                    //        {
                    //            if (file.ToLower().EndsWith(ext))
                    //            {
                    //                allow = true;
                    //                break;
                    //            }
                    //        }
                    //        if (!allow)
                    //        {
                    //            Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... not allow FileType[{0}]", file.Substring(file.LastIndexOf('.') + 1));
                    //            return 1;
                    //        }
                    //    }
                    //}
#if DEBUGPROPERTY
                    Common.OutputDebugString("AddPages(delegate, IntPtr):: start. (MultiFiles)");
#endif
                }
                else
                {
                    Common.OutputDebugString("AddPages(delegate, IntPtr):: failed... not allow MultiFiles");
                    return 1;
                }
            }
#if DEBUGPROPERTY
            else
            {
                Common.OutputDebugString("AddPages(delegate, IntPtr):: start.");
            }
#endif
            try
            {
                PropertySheetTemplate propTemplate = OnGetTemplate();

                IntPtr hPage;
                DLGTEMPLATE template = default(DLGTEMPLATE);
                m_PropSheet.dwSize = Marshal.SizeOf(m_PropSheet);
                m_PropSheet.dwFlags = PSP_FLAGS.PSP_USETITLE | PSP_FLAGS.PSP_USECALLBACK | PSP_FLAGS.PSP_DLGINDIRECT;
                if (propTemplate.RightToLeft == RightToLeft.Yes)
                {
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_RTLREADING;
                }

                Bitmap icon = propTemplate.Icon;
                if (icon != null)
                {
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_USEHICON;
                    m_PropSheet.hIcon = icon.GetHicon();
                }
                if (propTemplate.ShowHelpButton)
                {
                    m_PropSheet.dwFlags |= PSP_FLAGS.PSP_HASHELP;
                }

                template.Style = (int)(WindowStyles.WS_VISIBLE | WindowStyles.WS_TABSTOP);
                m_PropSheet.pResource = Marshal.AllocCoTaskMem(Marshal.SizeOf(template));
                Marshal.StructureToPtr(template, m_PropSheet.pResource, false);

                m_PropSheet.pfnDlgProc = new WndProc(this.PageProc);
                m_PropSheet.pfnCallback = new PropertySheetCallback(this.Callback);
                m_PropSheet.pszTitle = propTemplate.Text;

                hPage = PropertySheetBase.CreatePropertySheetPageW(ref m_PropSheet);

                if (hPage.ToInt32() != 0)
                {
                    if (!lpfnAddPage(hPage, lParam))
                    {
                        Common.OutputDebugString("AddPages(delegate, IntPtr):: failed. 'lpfnAddPage' returns false.");
                        PropertySheetBase.DestroyPropertySheetPage(hPage);
                    }
#if DEBUGPROPERTY
                    else Common.OutputDebugString("AddPages(delegate, IntPtr):: OK.");
#endif
                }
                return 0;
            }
            catch (Exception e)
            {
                Common.OutputError("AddPages(delegate, IntPtr)", e);
            }
            return 1;
        }
        void IShellPropSheetExt.ReplacePage(uint uPageID, IntPtr lpfnReplaceWith, IntPtr lParam) { }

        #endregion

        #region - IShellExtInit 成员 -

        void IShellExtInit.Initialize(IntPtr pidlFolder, System.Runtime.InteropServices.ComTypes.IDataObject lpIDataObject, uint hkeyProgID)
        {
            m_Files = new FileCollection(lpIDataObject);
        }

        #endregion

        #region - COM 注册 -

        [System.Runtime.InteropServices.ComRegisterFunction()]
        static void ComReg(Type t)
        {
            Common.RegisterExtension(t, "PropertySheetHandlers\\" + t.FullName);
            bool changed = false;
            foreach (PropertySheetBaseProvider provider in Extension.Where(Common.Providers, p => p.Type == ProviderType.PropertySheet))
            {
                foreach (string ext in provider.Exts)
                {
                    changed |= Common.RegisterKey(ext, "PropertySheetHandlers\\" + t.FullName, ProviderType.PropertySheet);
                }
            }
            // 注册了键值就刷新explorer
            if (changed)
            {
                Common.SHChangeNotify(Common.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
            }
        }

        [System.Runtime.InteropServices.ComUnregisterFunction()]
        static void ComUnreg(Type t)
        {
            foreach (PropertySheetBaseProvider provider in Extension.Where(Common.Providers, p => p.Type == ProviderType.PropertySheet))
            {
                foreach (string ext in provider.Exts)
                {
                    Common.UnregisterKey(ext, "PropertySheetHandlers\\" + t.FullName, ProviderType.PropertySheet);
                }
            }
            Common.UnregisterExtension(t, "PropertySheetHandlers\\" + t.FullName);
        }

        #endregion

    }
}
